﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace XmlVisualizer.DiagramDesigner.Controls {
    /// <summary>
    /// Represents the object which when placed on other object it can cause the change of its size.
    /// </summary>
    public class ResizeThumb : Thumb {
		#region Constructors
		/// <summary>
		/// Initializes a new instance of the ResizeThumb.
		/// </summary>
		public ResizeThumb() {
			// add the event handler for the event DragDelta
			base.DragDelta += new DragDeltaEventHandler(ResizeThumb_DragDelta);
		}
		#endregion

		#region Event handlers
		/// <summary>
		/// DragDelta event handler.
		/// </summary>
		/// <param name="sender">The object where the event handler is attached.</param>
		/// <param name="e">The event data.</param>
		void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e) {
			DesignerItem designerItem = this.DataContext as DesignerItem;
			DesignerCanvas designer = VisualTreeHelper.GetParent(designerItem) as DesignerCanvas;

			if (designerItem != null && designer != null && designerItem.IsSelected) {
				double minLeft, minTop, minDeltaHorizontal, minDeltaVertical;
				double dragDeltaVertical, dragDeltaHorizontal, scale;

				IEnumerable<DesignerItem> selectedDesignerItems = designer.selectionService.CurrentSelection.OfType<DesignerItem>();

				CalculateDragLimits(selectedDesignerItems, out minLeft, out minTop, out minDeltaHorizontal, out minDeltaVertical);

				foreach (DesignerItem item in selectedDesignerItems) {
					if (item != null && item.ParentID == Guid.Empty) {
						switch (base.VerticalAlignment) {
							case VerticalAlignment.Bottom:
								dragDeltaVertical = Math.Min(-e.VerticalChange, minDeltaVertical);
								scale = (item.ActualHeight - dragDeltaVertical) / item.ActualHeight;
								DragBottom(scale, item, designer.selectionService);
								break;
							case VerticalAlignment.Top:
								double top = Canvas.GetTop(item);
								dragDeltaVertical = Math.Min(Math.Max(-minTop, e.VerticalChange), minDeltaVertical);
								scale = (item.ActualHeight - dragDeltaVertical) / item.ActualHeight;
								DragTop(scale, item, designer.selectionService);
								break;
							default:
								break;
						}

						switch (base.HorizontalAlignment) {
							case HorizontalAlignment.Left:
								double left = Canvas.GetLeft(item);
								dragDeltaHorizontal = Math.Min(Math.Max(-minLeft, e.HorizontalChange), minDeltaHorizontal);
								scale = (item.ActualWidth - dragDeltaHorizontal) / item.ActualWidth;
								DragLeft(scale, item, designer.selectionService);
								break;
							case HorizontalAlignment.Right:
								dragDeltaHorizontal = Math.Min(-e.HorizontalChange, minDeltaHorizontal);
								scale = (item.ActualWidth - dragDeltaHorizontal) / item.ActualWidth;
								DragRight(scale, item, designer.selectionService);
								break;
							default:
								break;
						}
					}
				}
				e.Handled = true;
			}
		}
		#endregion
		
        #region Helper methods
        private void DragLeft(double scale, DesignerItem item, SelectionService selectionService) {
            IEnumerable<DesignerItem> groupItems = selectionService.GetGroupMembers(item).Cast<DesignerItem>();

            double groupLeft = Canvas.GetLeft(item) + item.Width;
            foreach (DesignerItem groupItem in groupItems) {
                double groupItemLeft = Canvas.GetLeft(groupItem);
                double delta = (groupLeft - groupItemLeft) * (scale - 1);
                Canvas.SetLeft(groupItem, groupItemLeft - delta);
                groupItem.Width = groupItem.ActualWidth * scale;
            }
        }

        private void DragTop(double scale, DesignerItem item, SelectionService selectionService) {
            IEnumerable<DesignerItem> groupItems = selectionService.GetGroupMembers(item).Cast<DesignerItem>();
            double groupBottom = Canvas.GetTop(item) + item.ActualHeight; // was Height
            foreach (DesignerItem groupItem in groupItems) {
                double groupItemTop = Canvas.GetTop(groupItem);
                double delta = (groupBottom - groupItemTop) * (scale - 1);
                Canvas.SetTop(groupItem, groupItemTop - delta);
                groupItem.Height = groupItem.ActualHeight * scale;
            }
        }

        private void DragRight(double scale, DesignerItem item, SelectionService selectionService) {
            IEnumerable<DesignerItem> groupItems = selectionService.GetGroupMembers(item).Cast<DesignerItem>();

            double groupLeft = Canvas.GetLeft(item);
            foreach (DesignerItem groupItem in groupItems) {
                double groupItemLeft = Canvas.GetLeft(groupItem);
                double delta = (groupItemLeft - groupLeft) * (scale - 1);

                Canvas.SetLeft(groupItem, groupItemLeft + delta);
                groupItem.Width = groupItem.ActualWidth * scale;
            }
        }

        private void DragBottom(double scale, DesignerItem item, SelectionService selectionService) {
            IEnumerable<DesignerItem> groupItems = selectionService.GetGroupMembers(item).Cast<DesignerItem>();
            double groupTop = Canvas.GetTop(item);
            foreach (DesignerItem groupItem in groupItems) {
                double groupItemTop = Canvas.GetTop(groupItem);
                double delta = (groupItemTop - groupTop) * (scale - 1);

                Canvas.SetTop(groupItem, groupItemTop + delta);
                groupItem.Height = groupItem.ActualHeight * scale;
            }
        }

        private void CalculateDragLimits(IEnumerable<DesignerItem> selectedItems, out double minLeft, out double minTop, out double minDeltaHorizontal, out double minDeltaVertical) {
            minLeft = double.MaxValue;
            minTop = double.MaxValue;
            minDeltaHorizontal = double.MaxValue;
            minDeltaVertical = double.MaxValue;

            // drag limits are set by these parameters: canvas top, canvas left, minHeight, minWidth
            // calculate min value for each parameter for each item
            foreach (DesignerItem item in selectedItems) {
                double left = Canvas.GetLeft(item);
                double top = Canvas.GetTop(item);

                minLeft = double.IsNaN(left) ? 0 : Math.Min(left, minLeft);
                minTop = double.IsNaN(top) ? 0 : Math.Min(top, minTop);

                minDeltaVertical = Math.Min(minDeltaVertical, item.ActualHeight - item.MinHeight);
                minDeltaHorizontal = Math.Min(minDeltaHorizontal, item.ActualWidth - item.MinWidth);
            }
        }
        #endregion
    }
}
